package com.study.crypto.basic.service;

import com.alibaba.fastjson.JSON;
import com.study.crypto.basic.config.RedisComponent;
import com.study.crypto.basic.dto.Constants;
import com.study.crypto.basic.dto.RequestRandomDto;
import com.study.crypto.basic.dto.ResponseRandomDto;
import com.study.crypto.basic.dto.SignInfoDto;
import com.study.crypto.basic.dto.ca.*;
import com.study.crypto.basic.entity.CertificateApply;
import com.study.crypto.basic.entity.Certification;
import com.study.crypto.basic.signer.Signer;
import com.study.crypto.basic.signer.SignerFactory;
import com.study.crypto.basic.signer.exception.MyRuntimeException;
import com.study.crypto.basic.utils.CertUtils;
import com.study.crypto.basic.utils.EssPdfUtil;
import com.study.crypto.basic.utils.KeyUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.Certificate;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pkcs.PKCSException;
import org.bouncycastle.util.encoders.Base64;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ResourceUtils;

import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;

/**
 * @author Songjin
 * @since 2022-11-01 14:55
 */
@Component
public class CertificateIssuerService implements InitializingBean {

    /** 服务器私钥 */
    private PrivateKey serverPrivateKey;
    /** 服务器证书 */
    private Certificate serverCert;
    private Certificate serverCertRSA;
    /** 客户端证书 */
    private Certificate   clientCert;
    private ContentSigner issuerSignerSM2;
    private ContentSigner issuerSignerECC;
    private ContentSigner issuerSignerRSA;
    private Signer signer;

    @Autowired
    private RedisComponent redis;
    @Autowired
    private CertificationService certService;
    @Autowired
    private CertificateApplyService certApplyService;

    /**
     * 获取随机数
     * @param request 请求数据
     * @return ResponseRandomDto
     */
    public ResponseRandomDto applyServiceRandom(RequestRandomDto request) {
        ResponseRandomDto response = new ResponseRandomDto();
        response.setResultCode(CaConstants.SUCCESS);
        response.setResultCodeMsg("随机数申请成功");
        response.setRandomB(EssPdfUtil.genRandomUuid().substring(0, 16));
        this.redis.set(request.getRandomA().concat(response.getRandomB()), "applyServiceRandom", 60L);
        return response;
    }

    /**
     * 证书申请请求，单证，只有签名证书
     * @param request 请求数据
     * @return ResponseSealCertDto
     */
    public ResponseCheckResultSealCertDto applySingleCert(RequestSealCertDistinctNameDto request) throws Exception {
        ResponseCheckResultSealCertDto response = new ResponseCheckResultChangeCertDto();

        // 验证客户端签名值
        SignInfoDto signInfo = request.getSignInfo();
        request.setSignInfo(null);
        byte[] signature = Base64.decode(signInfo.getSignValue());
        boolean verify = this.signer.verify(JSON.toJSONBytes(request), signature, this.clientCert);
        if (!verify) {
            response.setResultCode(Constants.VERIFY_FAILED);
            response.setResultCodeMsg("验签失败");
            return response;
        }

        // 将客户端证书申请录入数据库
        List<CertificateApply> applies = CertificateApply.getInstances(request);
        CertificateApply apply = applies.get(0);
        apply.setTaskId(EssPdfUtil.genRandomUuid());
        X500Name subject = CertUtils.buildDistinctName(apply.getDn());
        LocalDateTime notBefore = LocalDateTime.now();
        LocalDateTime notAfter = notBefore.plusYears(1);
        boolean sm2Or = GMObjectIdentifiers.sm2p256v1.getId().equals(apply.getAlgorithm());
        ContentSigner chooseIssuerSigner;
        Certificate chooseIssuerCert;
        String certType;
        if (sm2Or) {
            chooseIssuerSigner = issuerSignerSM2;
            chooseIssuerCert = serverCert;
            certType = Certification.CERT_TYPE_SM2;
        } else {
            chooseIssuerSigner = issuerSignerRSA;
            chooseIssuerCert = serverCertRSA;
            certType = Certification.CERT_TYPE_RSA;
        }
        String serial_ = certService.selectMaxSerialNumber(certType);
        BigInteger serial = new BigInteger(serial_, 16).add(BigInteger.valueOf(1));
        String publicKeyBase64 = apply.getSubjectPublicKeyInfo();
        Certificate signCert = CertUtils.generateUserCertificate(publicKeyBase64, subject, chooseIssuerSigner, chooseIssuerCert, notBefore, notAfter, serial);
        int signcertUsage = CertUtils.certificateUsage(signCert.getEncoded(ASN1Encoding.DER));
        Certification signCert_ = Certification.builder()
                                               .id(EssPdfUtil.genRandomUuid())
                                               .createTime(LocalDateTime.now())
                                               .updateTime(LocalDateTime.now())
                                               .beginTime(notBefore)
                                               .endTime(notAfter)
                                               .certDn(subject.toString())
                                               .keyId(apply.getKeystoreId())
                                               .certType(certType)
                                               .serialNumber(serial.toString(16).toUpperCase())
                                               .keyUsage(signcertUsage)
                                               .certificate(signCert.getEncoded(ASN1Encoding.DER))
                                               .applyId(apply.getId())
                                               .build();
        certService.insert(signCert_);
        certApplyService.insertList(applies);

        response.setResultCode(CaConstants.SUCCESS);
        response.setResultCodeMsg("证书申请成功");
        ResponseCheckResultSealCertDto.CheckResultSealCertDtoPackage package_ = new ResponseCheckResultSealCertDto.CheckResultSealCertDtoPackage();
        package_.setSignCert(Base64.toBase64String(signCert.getEncoded(ASN1Encoding.DER)));
        ResponseCheckResultSealCertDto.CheckResultSealCertDtoData certDtoData = new ResponseCheckResultSealCertDto.CheckResultSealCertDtoData();
        certDtoData.setRequestID(apply.getRequestId());
        certDtoData.setErrorCode(CaConstants.SUCCESS);
        certDtoData.setPackage_(package_);
        response.getData().add(certDtoData);
        return response;
    }

    /**
     * 证书申请请求
     * @param request 请求数据
     * @return ResponseSealCertDto
     */
    public ResponseSealCertDto applySealCert(RequestSealCertDto request) throws Exception {
        ResponseSealCertDto response = new ResponseSealCertDto();
        String tokenInfo = request.getTokenInfo();
        String redisVal = (String) redis.get(tokenInfo);
        if (StringUtils.isBlank(redisVal)) {
            response.setResultCode(CaConstants.APPLY_SEAL_CERT_FAILED);
            response.setResultCodeMsg("token过期或不存在");
            return response;
        }
        // tokeninfo 使用之后删除
        this.redis.del(tokenInfo);
        // 验证客户端签名值
        SignInfoDto signInfo = request.getSignInfo();
        request.setSignInfo(null);
        byte[] signature = Base64.decode(signInfo.getSignValue());
        boolean verify = this.signer.verify(JSON.toJSONBytes(request), signature, this.clientCert);
        if (!verify) {
            response.setResultCode(Constants.VERIFY_FAILED);
            response.setResultCodeMsg("验签失败");
            return response;
        }
        // 将客户端证书申请录入数据库
        List<CertificateApply> applies = CertificateApply.getInstances(request);
        String taskId = EssPdfUtil.genRandomUuid();
        applies.forEach(e -> e.setTaskId(taskId));
        certApplyService.insertList(applies);
        // 返回客户端数据
        response.setResultCode(CaConstants.SUCCESS);
        response.setResultCodeMsg("证书申请请求处理成功");
        response.getData().setTaskId(taskId);
        // 对返回数据进行签名
        byte[] signature_ = this.signer.sign(JSON.toJSONBytes(response), this.serverPrivateKey);
        SignInfoDto signInfo_ = new SignInfoDto();
        signInfo_.setSignValue(Base64.toBase64String(signature_));
        response.setSignInfo(signInfo_);
        return response;
    }

    /**
     * 使用taskId查询下载证书
     * @param request 请求数据
     * @return ResponseSealCertDto
     */
    public ResponseCheckResultSealCertDto checkResult(RequestCheckResultDto request) throws Exception {
        ResponseCheckResultSealCertDto response = new ResponseCheckResultChangeCertDto();

        String taskId = request.getData().getTaskId();
        CertificateApply apply_ = CertificateApply.builder().taskId(taskId).updateTime(null).build();
        CertificateApply apply = certApplyService.selectOne(apply_);

        Certification certification_ = Certification.builder().applyId(apply.getId()).updateTime(null).build();
        List<Certification> certifications = certService.select(certification_);
        byte[] envelopedKey;
        Certificate signCert;
        Certificate encCert;
        if (CollectionUtils.isEmpty(certifications)) {
            X500Name subject = CertUtils.buildDistinctName(apply.getCountryName(), apply.getOrganizationName(), apply.getCommonName());
            LocalDateTime notBefore = LocalDateTime.now();
            LocalDateTime notAfter = notBefore.plusYears(1);

            String serial_ = certService.selectMaxSerialNumber(Certification.CERT_TYPE_SM2);
            BigInteger serial = new BigInteger(serial_, 16).add(BigInteger.valueOf(1));
            BigInteger encCertSerial = serial.add(BigInteger.valueOf(1));
            String publicKeyBase64 = apply.getSubjectPublicKeyInfo();
            KeyPair keyPair = KeyUtils.generateKeyPair(KeyUtils.ALGO_SM2);
            signCert = CertUtils.generateUserCertificate(publicKeyBase64, subject, issuerSignerSM2, serverCert, notBefore, notAfter, serial);
            encCert = CertUtils.generateUserEncCertificate(keyPair, subject, issuerSignerSM2, serverCert, notBefore, notAfter, encCertSerial);
            int signcertUsage = CertUtils.certificateUsage(signCert.getEncoded(ASN1Encoding.DER));
            int enccertUsage = CertUtils.certificateUsage(encCert.getEncoded(ASN1Encoding.DER));
            envelopedKey = KeyUtils.generateEnvelopedKey(publicKeyBase64, keyPair);
            Certification signCert_ = Certification.builder()
                                                   .beginTime(notBefore)
                                                   .endTime(notAfter)
                                                   .certDn(subject.toString())
                                                   .keyId(apply.getKeystoreId())
                                                   .certType(Certification.CERT_TYPE_SM2)
                                                   .serialNumber(serial.toString(16).toUpperCase())
                                                   .keyUsage(signcertUsage)
                                                   .certificate(signCert.getEncoded(ASN1Encoding.DER))
                                                   .applyId(apply.getId())
                                                   .build();
            Certification encCert_ = new Certification();
            BeanUtils.copyProperties(signCert_, encCert_);
            encCert_.setKeyUsage(enccertUsage);
            encCert_.setCertificate(encCert.getEncoded(ASN1Encoding.DER));
            encCert_.setSerialNumber(encCertSerial.toString(16).toUpperCase());
            encCert_.setEnvelopedKey(envelopedKey);
            certService.insertCertificates(Arrays.asList(signCert_, encCert_));
        } else {
            Certification cert0 = certifications.get(0);
            Certification cert1 = certifications.get(1);
            boolean digitalSignature = CertUtils.usageDigitalSignature(cert0.getCertificate());
            if (digitalSignature) {
                envelopedKey = cert1.getEnvelopedKey();
                signCert = CertUtils.rebuildCertificate(cert0.getCertificate());
                encCert = CertUtils.rebuildCertificate(cert1.getCertificate());
            } else {
                envelopedKey = cert0.getEnvelopedKey();
                signCert = CertUtils.rebuildCertificate(cert1.getCertificate());
                encCert = CertUtils.rebuildCertificate(cert0.getCertificate());
            }
        }
        response.setResultCode(CaConstants.SUCCESS);
        response.setResultCodeMsg("证书申请成功");
        ResponseCheckResultSealCertDto.CheckResultSealCertDtoPackage package_ = new ResponseCheckResultSealCertDto.CheckResultSealCertDtoPackage();
        package_.setSignCert(Base64.toBase64String(signCert.getEncoded(ASN1Encoding.DER)));
        package_.setEncCert(Base64.toBase64String(encCert.getEncoded(ASN1Encoding.DER)));
        package_.setEncKeyProtection(Base64.toBase64String(envelopedKey));
        ResponseCheckResultSealCertDto.CheckResultSealCertDtoData certDtoData = new ResponseCheckResultSealCertDto.CheckResultSealCertDtoData();
        certDtoData.setRequestID(apply.getRequestId());
        certDtoData.setErrorCode(CaConstants.SUCCESS);
        certDtoData.setPackage_(package_);
        response.getData().add(certDtoData);
        return response;
    }

    @Override
    public void afterPropertiesSet() {
        try {
            // 客户端签名证书
            File clientCertFile = ResourceUtils.getFile("classpath:client-certs/sm2-client-cert.cer");
            byte[] clientCertBytes = FileUtils.readFileToByteArray(clientCertFile);
            this.clientCert = Certificate.getInstance(clientCertBytes);
            // 服务端证书
            File serverCertFile = ResourceUtils.getFile("classpath:certs/sm2-server-cert.cer");
            File serverCertFileRSA = ResourceUtils.getFile("classpath:certs/rsa-server-cert.cer");
            byte[] serverCertBytes = FileUtils.readFileToByteArray(serverCertFile);
            byte[] serverCertBytesRSA = FileUtils.readFileToByteArray(serverCertFileRSA);
            this.serverCert = Certificate.getInstance(serverCertBytes);
            this.serverCertRSA = Certificate.getInstance(serverCertBytesRSA);
            // 服务端私钥
            File serverKeyFile = ResourceUtils.getFile("classpath:certs/sm2-server-key.keystore");
            File serverKeyFileECC = ResourceUtils.getFile("classpath:certs/ecc-server-key.keystore");
            File serverKeyFileRSA = ResourceUtils.getFile("classpath:certs/rsa-server-key.keystore");
            String pemString = FileUtils.readFileToString(serverKeyFile, StandardCharsets.UTF_8);
            String pemStringECC = FileUtils.readFileToString(serverKeyFileECC, StandardCharsets.UTF_8);
            String pemStringRSA = FileUtils.readFileToString(serverKeyFileRSA, StandardCharsets.UTF_8);
            this.serverPrivateKey = KeyUtils.privateKey(pemString);
            PrivateKey serverPrivateKeyECC = KeyUtils.privateKey(pemStringECC);
            PrivateKey serverPrivateKeyRSA = KeyUtils.privateKey(pemStringRSA);
            this.signer = SignerFactory.produce(GMObjectIdentifiers.sm2sign_with_sm3);
            this.issuerSignerSM2 = new JcaContentSignerBuilder(Signer.SIGN_ALGO_SM3_SM2).setProvider(Signer.BC).build(serverPrivateKey);
            this.issuerSignerECC = new JcaContentSignerBuilder(Signer.SIGN_ALGO_SHA256_ECDSA).setProvider(Signer.BC).build(serverPrivateKeyECC);
            this.issuerSignerRSA = new JcaContentSignerBuilder(Signer.SIGN_ALGO_SHA256_RSA).setProvider(Signer.BC).build(serverPrivateKeyRSA);
        } catch (IOException | PKCSException | OperatorException e) {
            throw new MyRuntimeException(e);
        }
    }

}
